package com.dap.dubbo.rpc.utils;

import com.alibaba.dubbo.remoting.TimeoutException;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcResult;
import com.dap.dubbo.rpc.config.RedisConfig;
import org.mvel2.MVEL;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.compiler.ExpressionCompiler;
import org.redisson.api.RLock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.UUID;

public class HystrixUtils {
    /**
     * 超时数量
     */
    public int timeOutErrorNumber;
    /**
     * 错误登记时间间隔
     */
    public int timeOutInterval;
    /**
     * 异常错误登记时间
     */
    public int errorInterval;
    /**
     * 异常数量
     */
    public int errorNumber;
    /**
     * 异常比率错误登记时间
     */
    public int errorRateInterval;
    /**
     * 异常比率
     */
    public double errorRate;
    public String fail_back_fun;

    boolean iexists = false;
    String hystrixSwitch;

    private Invoker<?> invoker;
    private Invocation invocation;
    private String passNumbers;

    private int fusingtime;

    private Jedis jedis = RedisConfig.getJedis();

    //测试代码
    Result result =new RpcResult("");



    public HystrixUtils(Invoker<?> invoker, Invocation invocation) {
        this.invoker = invoker;
        this.invocation = invocation;

    }

    //主方法
    public Result execute() {




        String identifier = UUID.randomUUID().toString();

        String application = "FOP:Hystrix_" + invoker.getUrl().getParameter("group");
        //初始化参数
        parameterHandle(application);

        //初始化参数之后进行设置错误执行方法
        /*  ExpressionCompiler compiler = new ExpressionCompiler("fail_back_fun");
    CompiledExpression exp = compiler.compile();*/

        long now = System.currentTimeMillis();

        String locker = "lock"+application;

        //进入条件判断，开始加锁
        /**
         * 获取锁
         */
        RLock mylock = RedisConfig.getLock(locker);
        //加锁
        mylock.lock();

        //测试代码
        System.out.println(Thread.currentThread().getName() + "HystrixUtils,进入判断条件");

        try {
        //判断熔断状态打开时，进行半熔断判断，如果半熔断，放行部分交易，若处于熔断状态执行默认脚本（脚本程序执行不出来）

        if (jedis.get("hystrixSwitch").equals("open")) {
			//判断计时器是否存在，不存在则为半熔断状态
            if (jedis.get("hystrixlock") == null) {

                //测试代码
                System.out.println("服务半开,放行一部分交易量");

                String passNumber = jedis.get("passNumber");
                if (passNumber != null && Integer.valueOf(passNumber) > 0) {

                    try {
                        jedis.zadd(application + "_Request", now, identifier);
                        result = invoker.invoke(invocation);
                        //System.out.println(Thread.currentThread().getName() + "服务请求一次");
                        jedis.decr("passNumber");
                        return result;

                    } catch (Exception e) {
                        System.out.println("熔断状态失败，重新开启熔断");
                        jedis.set("passNumber", passNumbers);
                        jedis.setex("hystrixlock", fusingtime, "hystrixlock");
                        jedis.set("hystrixSwitch", "open");
                        return  new RpcResult("null");
                    }
                }
                if (Integer.valueOf(jedis.get("passNumber")) <= 0) {
                    System.out.println("交易通过，关闭熔断");
                    jedis.set("hystrixSwitch", "close");
                }
            }
            //计时器存在，熔断状态
            else {
                System.out.println("熔断开启中");

/*
                Object result = MVEL.executeExpression(exp);
                System.out.println(result);*/

                return new RpcResult("null");

            }
        }
        /**
         * 服务异常，拒绝服务
         */
        System.out.println(Thread.currentThread().getName() + "判断异常是否过多");
        if (isTimeOut(application, now) || isErrore(application, now) || isErroreRate(application, now)) {
            Transaction lock = jedis.multi();
            lock.set("hystrixSwitch", "open");
            lock.set("passNumber", passNumbers);
            lock.setex("hystrixlock", fusingtime, "hystrixlock");
            lock.exec();
            System.out.println("异常次数过多");

            //
           /* Object result = MVEL.executeExpression(exp);
            System.out.println(result);*/
            return new RpcResult("null");

        }


        /**
         * 正常执行，如果抛异常，记录异常。
         */
            System.out.println(Thread.currentThread().getName() + "判断完毕，继续执行");
            jedis.zadd(application + "_Request", now, identifier);
            Result result = invoker.invoke(invocation);
            return result;


        } catch (Exception e) {
            System.out.println(Thread.currentThread().getName() + "抛出异常");
            if (e.getCause() instanceof TimeoutException) {
                System.out.println(Thread.currentThread().getName() + "添加超时异常数据");
                jedis.zadd(application + "_TimeOutErroeNumber", now, identifier);
            } else {
                jedis.zadd(application + "_ErroeNumber", now, identifier);
            }
            //throw e;null
           /* Object result = MVEL.executeExpression(exp);
            System.out.println(result);*/
            return new RpcResult("null");
        } finally {

            System.out.println("jedis"+jedis);
            if (jedis != null) {
                jedis.close();
            }
            //解锁
            if (mylock.isLocked()) {
                mylock.unlock();
            }
        }

    }



//判断超时否过多，如果过多返回 true
    private Boolean isTimeOut(String application, long now) {
        //事务
        Transaction getdata = jedis.multi();
        //留下timeOutInterval到now的数据
        getdata.zremrangeByScore((application + "_TimeOutErroeNumber").getBytes(), "-inf".getBytes(),
                String.valueOf(now - timeOutInterval).getBytes());

        getdata.zcard(application + "_TimeOutErroeNumber");
        //事务执行
        List<Object> result = getdata.exec();

        //拿到数量
        Long count = (Long) result.get(result.size() - 1);

        System.out.println(Thread.currentThread().getName() + "熔断服务，抛超时异常的线程数" + count);

        if (count != null && count > this.timeOutErrorNumber) {
            return true;
        } else {
            return false;
        }

    }
    //判断错误否过多，如果过多返回 true
    private Boolean isErrore(String application, long now) {
        //开事务
        Transaction getdata = jedis.multi();
        getdata.zremrangeByScore((application + "_ErroeNumber").getBytes(), "-inf".getBytes(),
                String.valueOf(now - this.errorInterval).getBytes());
        //统计确定时间errorInterval内异常的线程数
        getdata.zcard(application + "_ErroeNumber");
        List<Object> result = getdata.exec();
        Long count = (Long) result.get(result.size() - 1);
        //System.out.println(Thread.currentThread().getName() + "熔断服务，异常次数" + count);
        if (count != null && count > this.errorNumber) {
            return true;
        } else {
            return false;
        }

    }
    //判断错误比率否过高，如果过高返回 true
    private Boolean isErroreRate(String application, long now) {
        Transaction getdata = jedis.multi();
        getdata.zremrangeByScore((application + "_Request").getBytes(), "-inf".getBytes(),
                String.valueOf(now - this.errorRateInterval).getBytes());
        getdata.zremrangeByScore((application + "ErroeNumber").getBytes(), "-inf".getBytes(),
                String.valueOf(now - this.errorRateInterval).getBytes());
        getdata.zcard(application + "_Request");
        getdata.zcard(application + "_ErroeNumber");
        List<Object> result = getdata.exec();

        Long request = (Long) result.get(result.size() - 2);
        Long error = (Long) result.get(result.size() - 1);

        if (error != null && request != null && request > 5) {
            Double errorrate = (double) ((float) error / (float) request);

            System.out.println(Thread.currentThread().getName() + "熔断服务，异常比率" + errorrate);

            if (errorrate != null && errorrate > this.errorRate) {
                return true;
            } else {
                return false;
            }
        }
        return false;

    }
//初始化参数
    private void parameterHandle(String application) {
        Properties properties = new Properties();
        InputStream resourceAsStream = (InputStream) HystrixUtils.class.getClassLoader()
                .getResourceAsStream("properties/hystrixpro.properties");
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        Transaction t = jedis.multi();
        t.get("FAIL_BACK_FUN");
        t.get("fusingtime");
        t.get("passNumbers");
        t.hget(application, "PER_SEC_FAIL_NUM");
        t.hget(application, "FAIL_NUM");
        t.hget(application, "PER_SEC_FAIL_RATE");
        t.hget(application, "FAIL_RATE");
        t.hget(application, "PER_SEC_AVG_TIMEOUT");
        t.hget(application, "AVG_TIMEOUT");
        List<Object> result=t.exec();
        String fail_back_fun = (String) result.get(0);
        if (fail_back_fun != null && !"".equals(fail_back_fun)) {
            this.fail_back_fun = fail_back_fun;
        } else {
            this.fail_back_fun = properties.getProperty("FAIL_BACK_FUN", "return null");
        }
        String fusingtime = (String) result.get(1);;
        if (fusingtime != null && !"".equals(fusingtime)) {
            this.fusingtime = Integer.valueOf(fusingtime);
        } else {
            this.fusingtime = Integer.valueOf(properties.getProperty("Fusingtime", "2"));
        }
        String passNumbers = (String) result.get(2);;
        if (passNumbers != null && !"".equals(passNumbers)) {
            this.passNumbers = passNumbers;
        } else {
            this.passNumbers = properties.getProperty("passNumbers", "2");
        }
        String errorInterval =(String) result.get(3);
        if (errorInterval != null && !"".equals(errorInterval)) {
            this.errorInterval = Integer.valueOf(errorInterval);
        } else {
            this.errorInterval =Integer.valueOf(properties.getProperty("PER_SEC_FAIL_NUM", "3000"));
        }
        String errorNumber = (String) result.get(4);
        if (errorNumber != null && !"".equals(errorNumber)) {
            this.errorNumber = Integer.valueOf(errorNumber);
        } else {
            this.errorNumber = Integer.valueOf(properties.getProperty("FAIL_NUM", "3"));
        }

        String errorRateInterval = (String) result.get(5);
        if (errorRateInterval != null && !"".equals(errorRateInterval)) {
            this.errorRateInterval = Integer.valueOf(errorRateInterval);
        } else {
            this.errorRateInterval = Integer.valueOf(properties.getProperty("PER_SEC_FAIL_RATE", "3000"));
        }
        String errorRate = (String) result.get(6);
        if (errorRate != null && !"".equals(errorRate)) {
            this.errorRate = Integer.valueOf(errorRate);
        } else {
            this.errorRate = Double.valueOf(properties.getProperty("FAIL_RATE", "0.8"));
        }

        String timeOutInterval = (String) result.get(7);
        if (timeOutInterval != null && !"".equals(timeOutInterval)) {
            this.timeOutInterval = Integer.valueOf(timeOutInterval);
        } else {
            this.timeOutInterval =Integer.valueOf(properties.getProperty("PER_SEC_AVG_TIMEOUT", "3000"));
        }
        String timeOutErrorNumber = (String) result.get(8);
        if (timeOutErrorNumber != null && !"".equals(timeOutErrorNumber)) {
            this.timeOutErrorNumber = Integer.valueOf(timeOutErrorNumber);
        } else {
            this.timeOutErrorNumber = Integer.valueOf(properties.getProperty("AVG_TIMEOUT", "3"));

        }

        if (jedis.get("hystrixSwitch")== null){
            jedis.set("hystrixSwitch","close");
        }



    }
}
